﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Flower.Services.Data;
using Jurassic;
using Jurassic.Library;
using Flower.Directory.Scripting.DirectoryEntries;

namespace Flower.Directory.Scripting
{
    public static class JsConvert
    {
        private class InputParametersMapper : IScriptParameterVisitor
        {
            private readonly ObjectInstance _in;

            public InputParametersMapper(ObjectInstance inObj)
            {
                _in = inObj;
            }

            public void Visit(BoolScriptParameter param)
            {
                _in.Set(param.Name, param.Value);
            }

            public void Visit(IntScriptParameter param)
            {
                _in.Set(param.Name, param.Value);
            }

            public void Visit(DoubleScriptParameter param)
            {
                _in.Set(param.Name, param.Value);
            }

            public void Visit(StringScriptParameter param)
            {
                _in.Set(param.Name, param.Value);
            }

            public void Visit(DirectoryEntryScriptParameter param)
            {
                _in[param.Name] = EntryToObject(_in.Engine, param.Value);
            }

            public void Visit(BoolArrayScriptParameter param)
            {
                _in.Set(param.Name, param.Value);
            }

            public void Visit(IntArrayScriptParameter param)
            {
                _in.Set(param.Name, param.Value);
            }

            public void Visit(DoubleArrayScriptParameter param)
            {
                _in.Set(param.Name, param.Value);
            }

            public void Visit(StringArrayScriptParameter param)
            {
                _in.Set(param.Name, param.Value);
            }

            public void Visit(DirectoryEntryArrayScriptParameter param)
            {
                var arr = _in.Engine.Array.Construct();

                foreach (DirectoryEntry ent in param.Value)
                {
                    arr.Push((object)EntryToObject(_in.Engine, ent));
                }

                _in[param.Name] = arr;
            }
        }

        private class EntryToObjectMapper : IDirectoryEntryVisitor
        {
            private ScriptEngine _engine;

            public ScriptEngine Engine { set { _engine = value; } }
            public DirectoryEntryObject Result { get; private set; }

            public void Visit(Assembly assembly)
            {
                Result = new AssemblyObject(_engine.Object.InstancePrototype, assembly);
            }

            public void Visit(Folder folder)
            {
                Result = new FolderObject(_engine.Object.InstancePrototype, folder);
            }

            public void Visit(Link link)
            {
                Result = new LinkObject(_engine.Object.InstancePrototype, link);
            }

            public void Visit(Message message)
            {
                Result = new MessageObject(_engine.Object.InstancePrototype, message);
            }

            public void Visit(Process process)
            {
                Result = new ProcessObject(_engine.Object.InstancePrototype, process);
            }

            public void Visit(Processor processor)
            {
                Result = new ProcessorObject(_engine.Object.InstancePrototype, processor);
            }

            public void Visit(Role role)
            {
                Result = new RoleObject(_engine.Object.InstancePrototype, role);
            }

            public void Visit(ServiceContainer serviceContainer)
            {
                Result = new ServiceContainerObject(_engine.Object.InstancePrototype, serviceContainer);
            }

            public void Visit(Set set)
            {
                Result = new SetObject(_engine.Object.InstancePrototype, set);
            }

            public void Visit(State state)
            {
                Result = new StateObject(_engine.Object.InstancePrototype, state);
            }

            public void Visit(Workflow workflow)
            {
                Result = new WorkflowObject(_engine.Object.InstancePrototype, workflow);
            }

            public void Visit(DirectoryEntryInfo directoryEntryInfo)
            {
                Result = new DirectoryEntryInfoObject(_engine.Object.InstancePrototype, directoryEntryInfo);
            }
        }

        public static DirectoryEntryObject EntryToObject(ScriptEngine engine, DirectoryEntry entry)
        {
            if (engine == null)
            {
                throw new ArgumentNullException("engine");
            }

            if (entry == null)
            {
                throw new ArgumentNullException("entry");
            }

            var mapper = new EntryToObjectMapper { Engine = engine };
            entry.AcceptVisitor(mapper);

            if (mapper.Result == null)
            {
                throw new ArgumentException(string.Format("Unknown entry type {0}.", entry.GetType().FullName), "entry");
            }

            return mapper.Result;
        }

        public static DirectoryEntry ObjectToEntry(ObjectInstance obj)
        {
            if (obj == null)
            {
                throw new ArgumentNullException("obj");
            }

            var entryObj = obj as DirectoryEntryObject;

            if (entryObj == null)
            {
                throw new ArgumentException(string.Format("Argument type {0} is invalid.", obj.GetType().FullName), "obj");
            }

            return entryObj.Entry;
        }

        public static ObjectInstance ScriptParamsToObject(ObjectInstance target, IEnumerable<ScriptParameter> parameters)
        {
            if (target == null)
            {
                throw new ArgumentNullException("target");
            }

            if (parameters == null)
            {
                throw new ArgumentNullException("parameters");
            }

            var mapper = new InputParametersMapper(target);

            foreach (ScriptParameter param in parameters)
            {
                param.AcceptVisitor(mapper);
            }

            return target;
        }

        public static ScriptParameter MakeScriptParameter(string name, object value)
        {
            if (value == null ||
                value is Jurassic.Null ||
                value is Jurassic.Undefined)
            {
                return null;
            }

            if (value is bool)
            {
                return new BoolScriptParameter
                {
                    Name = name,
                    Value = (bool)value
                };
            }
            else if (value is BooleanInstance)
            {
                return new BoolScriptParameter
                {
                    Name = name,
                    Value = ((BooleanInstance)value).Value
                };
            }
            else if (value is int)
            {
                return new IntScriptParameter
                {
                    Name = name,
                    Value = (int)value
                };
            }
            else if (value is double)
            {
                return new DoubleScriptParameter
                {
                    Name = name,
                    Value = (double)value
                };
            }
            else if (value is NumberInstance)
            {
                return new DoubleScriptParameter
                {
                    Name = name,
                    Value = ((NumberInstance)value).Value
                };
            }
            else if (value is string)
            {
                return new StringScriptParameter
                {
                    Name = name,
                    Value = (string)value
                };
            }
            else if (value is StringInstance)
            {
                return new StringScriptParameter
                {
                    Name = name,
                    Value = ((StringInstance)value).Value
                };
            }
            else if (value is DirectoryEntryObject)
            {
                return new DirectoryEntryScriptParameter
                {
                    Name = name,
                    Value = ((DirectoryEntryObject)value).Entry
                };
            }
            else if (value is DirectoryEntry)
            {
                return new DirectoryEntryScriptParameter
                {
                    Name = name,
                    Value = (DirectoryEntry)value
                };
            }
            else if (value is IList<bool>)
            {
                return new BoolArrayScriptParameter
                {
                    Name = name,
                    Value = (IList<bool>)value
                };
            }
            else if (value is IList<int>)
            {
                return new IntArrayScriptParameter
                {
                    Name = name,
                    Value = (IList<int>)value
                };
            }
            else if (value is IList<double>)
            {
                return new DoubleArrayScriptParameter
                {
                    Name = name,
                    Value = (IList<double>)value
                };
            }
            else if (value is IList<string>)
            {
                return new StringArrayScriptParameter
                {
                    Name = name,
                    Value = (IList<string>)value
                };
            }
            else if (value is IList<DirectoryEntry>)
            {
                return new DirectoryEntryArrayScriptParameter
                {
                    Name = name,
                    Value = (IList<DirectoryEntry>)value
                };
            }
            else if (value is ArrayInstance)
            {
                var arr = (ArrayInstance)value;

                if (arr.Length == 0)
                {
                    return null;
                }

                object firstElement = arr.ElementValues.First();

                try
                {
                    if (firstElement is bool)
                    {
                        return new BoolArrayScriptParameter
                        {
                            Name = name,
                            Value = arr.ElementValues.Select(el => (bool)el).ToList()
                        };
                    }
                    else if (firstElement is BooleanInstance)
                    {
                        return new BoolArrayScriptParameter
                        {
                            Name = name,
                            Value = arr.ElementValues.Select(el => ((BooleanInstance)el).Value).ToList()
                        };
                    }
                    else if (firstElement is int)
                    {
                        return new IntArrayScriptParameter
                        {
                            Name = name,
                            Value = arr.ElementValues.Select(el => (int)el).ToList()
                        };
                    }
                    else if (firstElement is uint)
                    {
                        return new IntArrayScriptParameter
                        {
                            Name = name,
                            Value = arr.ElementValues.Select(el => (int)(uint)el).ToList()
                        };
                    }
                    else if (firstElement is double)
                    {
                        return new DoubleArrayScriptParameter
                        {
                            Name = name,
                            Value = arr.ElementValues.Select(el => (double)el).ToList()
                        };
                    }
                    else if (firstElement is NumberInstance)
                    {
                        return new DoubleArrayScriptParameter
                        {
                            Name = name,
                            Value = arr.ElementValues.Select(el => ((NumberInstance)el).Value).ToList()
                        };
                    }
                    else if (firstElement is string)
                    {
                        return new StringArrayScriptParameter
                        {
                            Name = name,
                            Value = arr.ElementValues.Select(el => (string)el).ToList()
                        };
                    }
                    else if (firstElement is StringInstance)
                    {
                        return new StringArrayScriptParameter
                        {
                            Name = name,
                            Value = arr.ElementValues.Select(el => ((StringInstance)el).Value).ToList()
                        };
                    }
                    else if (firstElement is DirectoryEntryObject)
                    {
                        return new DirectoryEntryArrayScriptParameter
                        {
                            Name = name,
                            Value = arr.ElementValues.Select(el => ((DirectoryEntryObject)el).Entry).ToList()
                        };
                    }
                    else if (firstElement is DirectoryEntry)
                    {
                        return new DirectoryEntryArrayScriptParameter
                        {
                            Name = name,
                            Value = arr.ElementValues.Select(el => (DirectoryEntry)el).ToList()
                        };
                    }
                }
                catch (InvalidCastException)
                {
                    return null;
                }
            }

            return null;
        }

        public static IEnumerable<ScriptParameter> ObjectToScriptParams(ObjectInstance obj)
        {
            if (obj == null)
            {
                throw new ArgumentNullException("obj");
            }

            foreach (PropertyNameAndValue prop in obj.Properties)
            {
                ScriptParameter param = MakeScriptParameter(prop.Name, prop.Value);
                if (param != null)
                {
                    yield return param;
                }
            }
        }

        public static void CleanupObject(ObjectInstance obj)
        {
            if (obj == null)
            {
                throw new ArgumentNullException("obj");
            }

            foreach (PropertyNameAndValue prop in obj.Properties)
            {
                if (prop.Value != Jurassic.Null.Value)
                {
                    obj[prop.Name] = Jurassic.Null.Value;
                }
            }
        }

        public static string ObjectToString(object obj)
        {
            return (obj == null || obj is Null || obj is Undefined) ? null : obj.ToString();
        }
    }
}
